package cn.melonlib.code.generator.comp.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.melonlib.code.generator.model.entity.*;
import cn.melonlib.code.generator.model.enums.WorkerEntityType;
import cn.melonlib.code.generator.model.enums.WorkerType;
import cn.melonlib.cores.commons.comp.service.impl.WorkerBasicService;
import cn.melonlib.cores.commons.exceptions.WorkerServiceException;
import cn.melonlib.cores.commons.model.entity.ApiEntity;
import cn.melonlib.cores.commons.model.entity.IEntity;
import cn.melonlib.cores.model.anno.*;
import com.google.common.base.CaseFormat;
import freemarker.template.Template;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Service;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static cn.melonlib.code.generator.model.enums.WorkerType.*;


@Service
public class WorkerServiceDescriptionService {

    public WorkerServiceMap loadMap(Class<? extends WorkerBasicService<?>> serviceType){
        WorkerServiceMap workerServiceMap=new WorkerServiceMap();
        WorkerService workerService=AnnotationUtils.findAnnotation(serviceType, WorkerService.class);
        WorkerComment serviceComment=AnnotationUtils.findAnnotation(serviceType,WorkerComment.class);
        if(serviceComment!=null){
            workerServiceMap.setComment(serviceComment.value());
        }else{
            workerServiceMap.setComment("未提供");
        }
        String beanName = StringUtils.defaultIfBlank(AnnotationUtils.findAnnotation(serviceType, Service.class).value()
                , serviceType.getSimpleName().substring(0, 1).toLowerCase(Locale.ROOT) + serviceType.getSimpleName().substring(1));
        workerServiceMap.setModelName(StringUtils.defaultIfBlank(workerService.model().name(),workerService.model().modelType().getSimpleName()));

        Class<?> storeType=workerService.model().modelType();
        Class<?> voType= Objects.equals(workerService.model().voType(), IEntity.class)?storeType:workerService.model().voType();
        Class<?> inputType= Objects.equals(workerService.model().inputType(), IEntity.class)?storeType:workerService.model().inputType();
        Class<?> filterType= Objects.equals(workerService.model().filterType(), IEntity.class)?storeType:workerService.model().filterType();
        workerServiceMap.setViewName(StringUtils.defaultIfBlank(workerService.model().name(),voType.getSimpleName())+"View");
        workerServiceMap.setInputName(StringUtils.defaultIfBlank(workerService.model().name(),inputType.getSimpleName())+"Input");
        workerServiceMap.setFilterName(StringUtils.defaultIfBlank(workerService.model().name(),filterType.getSimpleName())+"Filter");
        List<Class<?>> extendsTypes= Arrays.stream(workerService.model().extendsTypes()).collect(Collectors.toList());
//        List<Class<?>> dependenceTypes= Arrays.stream(workerService.model().dependenceTypes()).toList();
        workerServiceMap.setModule(workerService.module());
        workerServiceMap.setBeanName(beanName);
        workerServiceMap.setName(serviceType.getSimpleName());
        List<Method> methods= Arrays.stream(serviceType.getMethods()).collect(Collectors.toList());
        workerServiceMap.setQueries(methods.stream().filter(m->AnnotationUtils.findAnnotation(m, WorkerQuery.class)!=null)
                        .filter(m-> !ArrayUtil.contains(workerService.ignores(),m.getName()))
                .reduce(new WorkerServiceQueriesMap(),(map, method)->{
                    WorkerQuery workerQuery=AnnotationUtils.findAnnotation(method,WorkerQuery.class);
                    WorkerServiceQueryMap workerServiceQueryMap=new WorkerServiceQueryMap();
                    String name=StringUtils.defaultIfBlank(workerQuery.name(),method.getName());
                    workerServiceQueryMap.setName(name);
                    workerServiceQueryMap.setQueryMode(workerQuery.mode().name());
                    workerServiceQueryMap.setCachePolicy(workerQuery.cachePolicy().getFlag());
                    WorkerComment workerComment=AnnotationUtils.findAnnotation(method,WorkerComment.class);
                    if(workerComment!=null) {
                        workerServiceQueryMap.setComment(workerComment.value());
                    }else{
                        workerServiceQueryMap.setComment("未提供");
                    }
                    workerServiceQueryMap.setParams(getParamsMap(method,WorkerEntityType.FILTER));
                    workerServiceQueryMap.setResult(getReturnType(method,WorkerEntityType.VIEW));
//                    workerServiceQueryMap.setResult();
                    map.put(name,workerServiceQueryMap);
                    return map;
                },(map,m)->null)
        );

        workerServiceMap.setMutations(methods.stream().filter(m->AnnotationUtils.findAnnotation(m,WorkerStore.class)!=null)
                .filter(m-> !ArrayUtil.contains(workerService.ignores(),m.getName()))
                .reduce(new WorkerServiceMutationsMap(),(map,method)->{
                    WorkerStore workerStore=AnnotationUtils.findAnnotation(method,WorkerStore.class);
                    WorkerServiceMutationMap workerServiceMutationMap=new WorkerServiceMutationMap();
                    String name=StringUtils.defaultIfBlank(workerStore.name(),method.getName());
                    workerServiceMutationMap.setName(name);
                    WorkerComment workerComment=AnnotationUtils.findAnnotation(method,WorkerComment.class);
                    if(workerComment!=null) {
                        workerServiceMutationMap.setComment(workerComment.value());
                    }else{
                        workerServiceMutationMap.setComment("未提供");
                    }
                    workerServiceMutationMap.setParams(getParamsMap(method,WorkerEntityType.INPUT));
                    workerServiceMutationMap.setResult(getReturnType(method,WorkerEntityType.VIEW));
//                    workerServiceQueryMap.setResult();
                    map.put(name,workerServiceMutationMap);
                    return map;
                },(map,m)->null)
        );

        WorkerServiceTypesMap typesMap=new WorkerServiceTypesMap();

        WorkerServiceTypeMap storeTypeMap=new WorkerServiceTypeMap();
        storeTypeMap.setName(workerServiceMap.getModelName()+"Store");
        ModelCommentMap commentMap=getComment(AnnotationUtils.findAnnotation(storeType,ModelComment.class));
        storeTypeMap.setComment(commentMap.value);
        storeTypeMap.setWorkerEntityType(WorkerEntityType.STORE);
        storeTypeMap.setFields(getFieldsMap(storeType,storeTypeMap.getWorkerEntityType()));
        typesMap.append(workerServiceMap.getModelName()+"Store",storeTypeMap);

        WorkerServiceTypeMap viewTypeMap=new WorkerServiceTypeMap();
        viewTypeMap.setName(workerServiceMap.getViewName());
        ModelCommentMap voCommentMap=getComment(AnnotationUtils.findAnnotation(voType,ModelComment.class));
        viewTypeMap.setComment(voCommentMap.value);
        viewTypeMap.setWorkerEntityType(Objects.equals(storeType,voType)?WorkerEntityType.STORE:WorkerEntityType.VIEW);
        viewTypeMap.setFields(getFieldsMap(voType,viewTypeMap.getWorkerEntityType()));
        typesMap.append(workerServiceMap.getViewName(),viewTypeMap);

        WorkerServiceTypeMap inputTypeMap=new WorkerServiceTypeMap();
        inputTypeMap.setName(workerServiceMap.getInputName());
        ModelCommentMap inputCommentMap=getComment(AnnotationUtils.findAnnotation(inputType,ModelComment.class));
        inputTypeMap.setComment(inputCommentMap.value);
        inputTypeMap.setWorkerEntityType(Objects.equals(storeType,voType)?WorkerEntityType.STORE:WorkerEntityType.INPUT);
        inputTypeMap.setFields(getFieldsMap(inputType,inputTypeMap.getWorkerEntityType()));
        typesMap.append(workerServiceMap.getInputName(),inputTypeMap);

        WorkerServiceTypeMap filterTypeMap=new WorkerServiceTypeMap();
        filterTypeMap.setName(workerServiceMap.getFilterName());
        ModelCommentMap filterCommentMap=getComment(AnnotationUtils.findAnnotation(filterType,ModelComment.class));
        filterTypeMap.setComment(filterCommentMap.value);
        filterTypeMap.setWorkerEntityType(WorkerEntityType.FILTER);
        filterTypeMap.setFields(getFieldsMap(filterType,filterTypeMap.getWorkerEntityType()));
        typesMap.append(workerServiceMap.getFilterName(),filterTypeMap);

        extendsTypes.stream().distinct().forEach(clazz->{
            WorkerServiceTypeMap extTypeMap=new WorkerServiceTypeMap();
            extTypeMap.setName(clazz.getSimpleName());
            ModelCommentMap extCommentMap=getComment(AnnotationUtils.findAnnotation(clazz,ModelComment.class));
            extTypeMap.setComment(extCommentMap.value);
            extTypeMap.setWorkerEntityType(WorkerEntityType.EXTENDS);
            extTypeMap.setFields(getFieldsMap(clazz,extTypeMap.getWorkerEntityType()));
            typesMap.append(extTypeMap.getName(),extTypeMap);
        });
        workerServiceMap.setTypes(typesMap);
//        typesMap.append("store",getFieldsMap(storeType,WorkerEntityType.STORE));
//        typesMap.append("store",getFieldsMap(storeType,WorkerEntityType.STORE));

        return workerServiceMap;
    }

    public List<String> dependenciesTypes(Class<? extends WorkerBasicService<?>> serviceType){
        WorkerService workerService=AnnotationUtils.findAnnotation(serviceType, WorkerService.class);
        return Arrays.stream(workerService.model().dependenceTypes()).map(this::importTypes).collect(Collectors.toList());
    }

    public String importTypes(Class<?> serviceType){
        WorkerService workerService=AnnotationUtils.findAnnotation(serviceType, WorkerService.class);
        String beanName = StringUtils.defaultIfBlank(AnnotationUtils.findAnnotation(serviceType, Service.class).value()
                , serviceType.getSimpleName().substring(0, 1).toLowerCase(Locale.ROOT) + serviceType.getSimpleName().substring(1));
        String modelName= StringUtils.defaultIfBlank(workerService.model().name(),workerService.model().modelType().getSimpleName());
        String servicePath= CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE,modelName);
        String fileName= CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE,beanName);
        String path= "'../../../"+workerService.module()+"/"+servicePath+"/model/" + fileName + ".model'";
        return "import {"+loadTypes(serviceType).stream().collect(Collectors.joining(","))+"} from "+path;
    }

    public Set<String> loadTypes(Class<?> serviceType){
        WorkerServiceMap workerServiceMap=new WorkerServiceMap();
        WorkerService workerService=AnnotationUtils.findAnnotation(serviceType, WorkerService.class);
        WorkerComment serviceComment=AnnotationUtils.findAnnotation(serviceType,WorkerComment.class);
        if(serviceComment!=null){
            workerServiceMap.setComment(serviceComment.value());
        }else{
            workerServiceMap.setComment("未提供");
        }
        String beanName = StringUtils.defaultIfBlank(AnnotationUtils.findAnnotation(serviceType, Service.class).value()
                , serviceType.getSimpleName().substring(0, 1).toLowerCase(Locale.ROOT) + serviceType.getSimpleName().substring(1));
        workerServiceMap.setModelName(StringUtils.defaultIfBlank(workerService.model().name(),workerService.model().modelType().getSimpleName()));

        Class<?> storeType=workerService.model().modelType();
        Class<?> voType= Objects.equals(workerService.model().voType(), IEntity.class)?storeType:workerService.model().voType();
        Class<?> inputType= Objects.equals(workerService.model().inputType(), IEntity.class)?storeType:workerService.model().inputType();
        Class<?> filterType= Objects.equals(workerService.model().filterType(), IEntity.class)?storeType:workerService.model().filterType();
        workerServiceMap.setViewName(StringUtils.defaultIfBlank(workerService.model().name(),voType.getSimpleName())+"View");
        workerServiceMap.setInputName(StringUtils.defaultIfBlank(workerService.model().name(),inputType.getSimpleName())+"Input");
        workerServiceMap.setFilterName(StringUtils.defaultIfBlank(workerService.model().name(),filterType.getSimpleName())+"Filter");
        List<Class<?>> extendsTypes= Arrays.stream(workerService.model().extendsTypes()).collect(Collectors.toList());
        List<Class<?>> dependenceTypes= Arrays.stream(workerService.model().dependenceTypes()).toList();
        workerServiceMap.setModule(workerService.module());
        workerServiceMap.setBeanName(beanName);
        workerServiceMap.setName(serviceType.getSimpleName());
        Set<String> types=new HashSet<>();
        types.add(workerServiceMap.getModelName()+"Store");
        types.add(workerServiceMap.getViewName());
        types.add(workerServiceMap.getInputName());
        types.add(workerServiceMap.getFilterName());
        types.addAll(extendsTypes.stream().distinct().map(Class::getSimpleName).collect(Collectors.toSet()));
        return types;
    }

    public List<WorkerServiceParam> getParamsMap(Method method, WorkerEntityType entityType){
        return Arrays.stream(method.getParameters()).reduce(new ArrayList<>(), (list,parameter) -> {
            Class<?> type=parameter.getType();
            WorkerServiceParam workerServiceParam=new WorkerServiceParam();
            if(List.class.isAssignableFrom(type)) {
                Class listType = ResolvableType.forMethodParameter(method, list.size()).getGeneric(0).toClass();
                workerServiceParam.setWorkerType(getWorkerType(listType, true, entityType));
            }else{
                workerServiceParam.setWorkerType(getWorkerType(type,false , entityType));
            }
            WorkerParameter workerParameter=AnnotationUtils.findAnnotation(parameter, WorkerParameter.class);
            workerServiceParam.setTypeName(parameter.getType().getName());
            workerServiceParam.setIdx(list.size());
            workerServiceParam.setComment(workerParameter.comment());
            if(workerParameter!=null){
                workerServiceParam.setName(workerParameter.value());
                workerServiceParam.setRequired(workerParameter.required());
            }else{
                workerServiceParam.setName(parameter.getName());
                workerServiceParam.setRequired(false);
            }
            list.add(workerServiceParam);
            return list;
        },(list,p)->null);
    }

    public List<WorkerServiceField> getFieldsMap(final Class<?> clazz,WorkerEntityType workerEntityType){
        Class<?> readClazz=clazz;
        if(Objects.equals(readClazz,Object.class)){
            throw new WorkerServiceException("传入的类型不能是java.lang.Object类型");
        }
        List<WorkerServiceField> fields=new ArrayList<>();
        do {
            fields.addAll(Arrays.stream(readClazz.getDeclaredFields()).reduce(new ArrayList<>(),(fs,f)->{
                WorkerServiceField workerServiceField=new WorkerServiceField();
                workerServiceField.setName(f.getName());
                ModelComment modelComment=AnnotationUtils.findAnnotation(f,ModelComment.class);
                if(modelComment!=null) {
                    workerServiceField.setComment(modelComment.value());
                    workerServiceField.setRequired(modelComment.required());
                }else{
                    workerServiceField.setComment("未说明");
                    workerServiceField.setRequired(false);
                }
                if(List.class.isAssignableFrom(f.getType())) {
                    Class<?> listType=ResolvableType.forField(f).getGeneric(0).toClass();
                    workerServiceField.setWorkerType(getWorkerType(listType, true, workerEntityType));
                    workerServiceField.setTypeName(listType.getName());
                }else{
                    workerServiceField.setWorkerType(getWorkerType(f.getType(), false, workerEntityType));
                    workerServiceField.setTypeName(f.getType().getName());
                }
                fs.add(workerServiceField);
              return fs;
            },(fs,f)->null));
            readClazz=readClazz.getSuperclass();
        }while(!Objects.equals(readClazz,Object.class));
        return fields;
    }

    public WorkerType getReturnType(Method method,WorkerEntityType entityType){
        Class<?> type=method.getReturnType();
        if(List.class.isAssignableFrom(type)) {
            Class listType = ResolvableType.forMethodReturnType(method).getGeneric(0).toClass();
            return getWorkerType(listType,true,entityType);
        }else{
            return getWorkerType(type,false,entityType);
        }
    }

    public WorkerType getWorkerType(Type type, boolean isList, WorkerEntityType entityType){
        if(Objects.equals(type,Integer.TYPE)||Objects.equals(type,Integer.class)){
            return isList?LIST_INT:INT;
        }else if(Objects.equals(type,Long.TYPE)||Objects.equals(type,Long.class)){
            return isList?LIST_LONG:LONG;
        }else if(Objects.equals(type,Double.TYPE)||Objects.equals(type,Double.class)){
            return  isList?LIST_DOUBLE:DOUBLE;
        }else if(Objects.equals(type,Boolean.TYPE)||Objects.equals(type,Boolean.class)){
            return  isList?LIST_BOOLEAN:BOOLEAN;
        }else if(Objects.equals(type,String.class)){
            return isList?LIST_STRING:STRING;
        }else if((type instanceof Class)&& ((Class<?>) type).isEnum()){
            return isList?LIST_ENUMS:ENUMS;
        }else{
            if(ApiEntity.class.isAssignableFrom((Class<?>) type)){
                return isList?LIST_API_ENTITY:API_ENTITY;
            }
            switch (entityType){
                case VIEW:return isList?LIST_VIEW_ENTITY :VIEW_ENTITY;
                case INPUT:return isList?LIST_INPUT_ENTITY :INPUT_ENTITY;
                case FILTER:return isList?LIST_FILTER_ENTITY :FILTER_ENTITY;
                default:return isList?LIST_STORE_ENTITY:STORE_ENTITY;
            }

        }
    }


    public ModelCommentMap getComment(ModelComment modelComment){
        if(modelComment!=null){
            return new ModelCommentMap(modelComment.value(),modelComment.required());
        }
        return new ModelCommentMap("未提供",false);
    }
    @Data
    public static class ModelCommentMap{
        public ModelCommentMap(String value, boolean required) {
            this.value = value;
            this.required = required;
        }

        private String value;
        private boolean required;
    }
}
